﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Communication.Local.LRPC;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Web;
using UtilZ.Dotnet.Ex.Json;
using UtilZ.Dotnet.Ex.Log;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Tool.HostCore.Model;
using UtilZ.Tool.HostPluginBase;

namespace UtilZ.Tool.HostCore
{
    public partial class HostBLL : IDisposable
    {

        private readonly HttpServer _httpServer;
        private readonly List<WebSocketClient> _tmpWebSocketList = new List<WebSocketClient>();
        private readonly ConcurrentDictionary<string, WebSocketClientCollection> _pluginWebSocketClientListdic = new ConcurrentDictionary<string, WebSocketClientCollection>();
        private readonly WebSocketClientCollection _logWebSocketClients = new WebSocketClientCollection();

        private readonly IReadOnlyDictionary<string, IHostPlugin> _pluginDic;
        private readonly IReadOnlyList<IHostPlugin> _pluginOrderList;
        protected readonly IReadOnlyList<PluginHomePageInfo> _pluginHomePageInfoList;

        private readonly ProcessMonitor _protectorAppProcessMonitor;
        private readonly Action _exitCallback;
        public HostBLL(ProcessMonitor appProtector, Action exitCallback)
        {
            this._protectorAppProcessMonitor = appProtector;
            this._exitCallback = exitCallback;

            var config = Config.Instance;
#if DEBUG
            var dirInfo = new DirectoryInfo(PathEx.GetEntryAssemblyDirectory());
            while (!string.Equals(dirInfo.Name, "App", StringComparison.OrdinalIgnoreCase))
            {
                dirInfo = dirInfo.Parent;
            }

            string rootDir = PathEx.Combine(PathType.Local, dirInfo.Parent.FullName, @"Core\UtilZ.Tool.HostCore\htmls");
#else
            string rootDir = PathEx.GetFullPath(@"htmls");
#endif

            LRPCCore.CreateChannelF(LRPCHelper.GET_WATCH_DOG_HTTP_PORT, (obj) =>
            {
                return config.HttpPort;
            });
            LRPCCore.CreateChannelA(LRPCHelper.PUBLISH_LOG, this.PublishLogCallback);

            //************************************************************************************************

            this.LoadBaseLib();
            this._pluginDic = this.LoadPlugin();
            this._pluginOrderList = this.CreatePluginOrderList();


            List<string> sourceDirs = new List<string>() { "htmls" };
            var apiMiddleware = new ApiMiddleware();
            apiMiddleware.AddApi(this);

            var pluginHomePageInfoList = new List<PluginHomePageInfo>();
            WebResourceInfo webResourceInfo;
            bool enableWebsoket = false;

            foreach (var plugin in this._pluginOrderList)
            {
                try
                {
                    plugin.Init();

                    webResourceInfo = plugin.GetWebResourceInfo();
                    if (webResourceInfo == null)
                    {
                        //web资源信息对象为null表示无相关信息
                        continue;
                    }

                    //是否启用websocket
                    if (webResourceInfo.EnableWebsocket)
                    {
                        enableWebsoket = true;
                        plugin.SendWebsocketMessage += Plugin_SendWebsocketMessage;
                    }

                    //交互页面
                    if (!string.IsNullOrEmpty(webResourceInfo.Url))
                    {
                        pluginHomePageInfoList.Add(new PluginHomePageInfo(webResourceInfo.Name, webResourceInfo.Description, webResourceInfo.Url));
                    }

                    //api对象
                    if (webResourceInfo.ApiObjs != null)
                    {
                        foreach (var apiIns in webResourceInfo.ApiObjs)
                        {
                            apiMiddleware.AddApi(apiIns);
                        }
                    }

                    //http资源路径
                    if (webResourceInfo.HttpSourcePaths != null)
                    {
                        sourceDirs.AddRange(webResourceInfo.HttpSourcePaths);
                    }

                    Loger.Info($"插件{plugin.ID}-{plugin.Name}初始化完成...");
                }
                catch (Exception ex)
                {
                    Loger.Error(ex, $"插件\"{plugin.Name}\"初始化异常...");
                }
            }

            //测试代码
            //for (int i = 0; i < 5; i++)
            //{
            //    pluginHomePageInfoList.Add(new PluginHomePageInfo($"Name{i}", "des", "url"));
            //}

            this._pluginHomePageInfoList = pluginHomePageInfoList;

            //http服务器
            var webSiteConfig = new WebSiteConfig(rootDir, @"host.html", sourceDirs);
            var handler = new HttpRequestHandler();

            if (enableWebsoket)
            {
                var websocketMiddleware = new WebsocketMiddleware(ValidateWebsocket, OutputWebsocket);
                handler.AddMiddleware(websocketMiddleware, 10);
            }

            handler.AddMiddleware(new WebSiteMiddleware(webSiteConfig), 100);
            handler.AddMiddleware(apiMiddleware, 20);

            var httpConfig = new HttpServerConfig()
            {
                ListenEndPoint = new IPEndPoint(IPAddress.Any, config.HttpPort)
            };
            this._httpServer = new HttpServer(httpConfig, handler);
        }



        #region websocket收发消息

        private void Plugin_SendWebsocketMessage(object sender, WebsocketSendMessageArgs e)
        {
            if (e.Targets != null && e.Targets.Any())
            {
                //只发送给指定的Websocket客户端
                foreach (var client in e.Targets)
                {
                    this.SendWebsocketMessage(client, e);
                }
            }
            else
            {
                //发送插件对应的所有Websocket客户端
                WebSocketClientCollection list;
                if (this._pluginWebSocketClientListdic.TryGetValue(e.PluginID, out list))
                {
                    lock (list.SyncRoot)
                    {
                        foreach (WebSocketClient client in list)
                        {
                            this.SendWebsocketMessage(client, e);
                        }
                    }
                }
            }
        }

        private void SendWebsocketMessage(WebSocketClient client, WebsocketSendMessageArgs e)
        {
            switch (e.MessageType)
            {
                case WebSocketMessageTypes.Text:
                    client.Send(e.Text);
                    break;
                case WebSocketMessageTypes.Binary:
                    DataSpan<byte> data = e.Data;
                    client.Send(data.Buffer, data.Offset, data.Length);
                    break;
                default:
                    throw new NotSupportedException(e.MessageType.ToString());
            }
        }

        private bool ValidateWebsocket(HttpRequest request)
        {
            return true;
        }

        private void OutputWebsocket(WebSocketClient webSocket)
        {
            webSocket.MessageOutput += WebSocket_MessageOutput;
            webSocket.ConnectionChanged += WebSocket_ConnectionChanged;
            lock (this._tmpWebSocketList)
            {
                this._tmpWebSocketList.Add(webSocket);
            }
            webSocket.StartReceive();
            Loger.Info("WebSocket 建立");
        }

        private void WebSocket_MessageOutput(object sender, MessageOutputArgs e)
        {
            try
            {
                var client = (WebSocketClient)sender!;
                string json = ((WebSocketTextMessage)e.Message).Text;
                var rcvMessage = JsonSerializer.DeserializeObject<ReceiveWebsocketMessage>(json);
                string pluginId = rcvMessage.PluginID;

                switch (rcvMessage.DataType)
                {
                    case ReceiveWebsocketMessage.AUTH:
                        WebSocketClientCollection list;
                        if (!this._pluginWebSocketClientListdic.TryGetValue(pluginId, out list))
                        {
                            lock (this._pluginWebSocketClientListdic)
                            {
                                if (!this._pluginWebSocketClientListdic.TryGetValue(pluginId, out list))
                                {
                                    list = new WebSocketClientCollection();
                                    this._pluginWebSocketClientListdic.Add(pluginId, list);
                                }
                            }
                        }

                        list.Add(client);
                        lock (this._tmpWebSocketList)
                        {
                            this._tmpWebSocketList.Remove(client);
                        }
                        break;
                    case ReceiveWebsocketMessage.DATA:
                        if (this._pluginDic.TryGetValue(pluginId, out IHostPlugin plugin))
                        {
                            plugin.WebSocketReceiveMessage(client, rcvMessage);
                        }
                        break;
                    case ReceiveWebsocketMessage.SUB_LOG:
                        this._logWebSocketClients.Add(client);
                        break;
                    default:
                        Loger.Warn($"未实现的消息类型\"{rcvMessage.DataType}\"");
                        break;
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }

        private void WebSocket_ConnectionChanged(object sender, NetConnectedChangedArgs e)
        {
            try
            {
                var webSocketClient = (WebSocketClient)sender!;
                if (e.Status == ConnectionStatus.Closed)
                {
                    webSocketClient.MessageOutput -= WebSocket_MessageOutput;
                    webSocketClient.ConnectionChanged -= WebSocket_ConnectionChanged;
                    webSocketClient.Dispose();

                    lock (this._tmpWebSocketList)
                    {
                        if (this._tmpWebSocketList.Remove(webSocketClient))
                        {
                            Loger.Warn("WebSocket 断开");
                            return;
                        }
                    }

                    if (!this._logWebSocketClients.Remove(webSocketClient))
                    {
                        foreach (var list in this._pluginWebSocketClientListdic.Values)
                        {
                            foreach (var client in list)
                            {
                                if (client == webSocketClient)
                                {
                                    list.Remove(webSocketClient);
                                    break;
                                }
                            }
                        }
                    }

                    Loger.Warn("WebSocket 断开");
                }
                else
                {
                    Loger.Warn("WebSocket 已连接上");
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }

        private readonly PublishLogItem _publishLogItem = new PublishLogItem();
        private void PublishLogCallback(object obj)
        {
            try
            {
                var logItem = (LogItem)obj;
                this._publishLogItem.Level = (int)logItem.Level;
                this._publishLogItem.Time = logItem.Time;
                this._publishLogItem.Message = logItem.Content;
                string json = JsonSerializer.SerializeObject(this._publishLogItem);

                foreach (WebSocketClient client in this._logWebSocketClients)
                {
                    try
                    {
                        client.Send(json);
                    }
                    catch (Exception exi)
                    {
                        Console.WriteLine(exi.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        #endregion


        private List<IHostPlugin> CreatePluginOrderList()
        {
            List<IHostPlugin> pluginOrderList = new List<IHostPlugin>();
            List<PluginLoadInfo> pluginLoadInfoList = Config.Instance.PluginLoadInfoList;
            if (pluginLoadInfoList != null && pluginLoadInfoList.Count > 0)
            {
                var pluginLoadInfoOrderArr = pluginLoadInfoList.OrderBy(t => { return t.OrderIndex; }).ToArray();

                foreach (var pluginInfo in pluginLoadInfoOrderArr)
                {
                    if (this._pluginDic.TryGetValue(pluginInfo.PluginID, out var plugin))
                    {
                        pluginOrderList.Add(plugin);
                    }
                    else
                    {
                        Loger.Warn($"有初始化顺序要求的插件\"{pluginInfo.PluginID}({pluginInfo.Des})\"不存在,忽略");
                    }
                }

                foreach (var plugin in this._pluginDic.Values)
                {
                    if (!pluginOrderList.Contains(plugin))
                    {
                        pluginOrderList.Add(plugin);
                    }
                }
            }
            else
            {
                pluginOrderList.AddRange(this._pluginDic.Values);
            }

            return pluginOrderList;
        }

        private IReadOnlyDictionary<string, IHostPlugin> LoadPlugin()
        {
            Dictionary<string, IHostPlugin> pluginDic = new Dictionary<string, IHostPlugin>();
            var pluginRootDir = PathEx.GetEntryAssemblyDirectory();
            string pluginBaseDir = PathEx.Combine(PathType.Local, pluginRootDir, "Plugins");
            if (!Directory.Exists(pluginBaseDir))
            {
                return pluginDic;
            }

            Dictionary<string, Assembly> assemblyDic = DictionaryEx.ToDictionary(AppDomain.CurrentDomain.GetAssemblies(), t => { return t.FullName; });
            IOSAdapter osAdapter = this.LoadOSAdapter(assemblyDic);
            string[] dllFilePathArr = Directory.GetFiles(pluginBaseDir, "*Plugin.dll", SearchOption.AllDirectories);
            string assemblyName;
            Assembly assembly;
            Type hostPluginType = typeof(IHostPlugin);
            IHostPlugin plugin, existPlugin;

            foreach (var dllFilePath in dllFilePathArr)
            {
                try
                {
                    assemblyName = UtilZ.Dotnet.Ex.Base.AssemblyEx.GetAssemblyName(dllFilePath);
                    if (string.IsNullOrWhiteSpace(assemblyName))
                    {
                        continue;
                    }

                    if (!assemblyDic.TryGetValue(assemblyName, out assembly))
                    {
                        assembly = Assembly.LoadFrom(dllFilePath);
                        if (assembly == null)
                        {
                            continue;
                        }

                        assemblyDic.Add(assembly.FullName, assembly);
                    }

                    Type[] typeArr = assembly.GetExportedTypes();
                    foreach (var type in typeArr)
                    {
                        if (!type.IsClass || type.IsAbstract)
                        {
                            continue;
                        }

                        if (type.GetInterface(hostPluginType.FullName) == null)
                        {
                            continue;
                        }

                        int noParameterConstructorCount = type.GetConstructors().Where(t => { return t.GetParameters().Length == 0; }).Count();
                        if (noParameterConstructorCount == 0)
                        {
                            Loger.Warn($"类型\"{type.FullName}\"没有无参构造函数,忽略");
                            continue;
                        }

                        plugin = ActivatorEx.CreateInstance<IHostPlugin>(type);
                        if (pluginDic.TryGetValue(plugin.ID, out existPlugin))
                        {
                            var existPluginType = existPlugin.GetType();
                            if (existPluginType == type)
                            {
                                Loger.Warn($"插件\"{existPluginType.FullName}\"和插件\"{type.FullName}\"的类型重复，忽略");
                                continue;
                            }

                            throw new ArgumentException($"插件\"{existPluginType.FullName}\"和插件\"{type.FullName}\"的标识重复");
                        }

                        plugin.OSAdapter = osAdapter;
                        pluginDic.Add(plugin.ID, plugin);
                        Loger.Info($"加载插件{plugin.ID}-{plugin.Name}");
                    }
                }
                catch (BadImageFormatException)
                {
                    continue;
                }
                catch (Exception ex)
                {
                    Loger.Error(ex);
                }
            }

            return pluginDic;
        }

        private IOSAdapter LoadOSAdapter(Dictionary<string, Assembly> assemblyDic)
        {
#pragma warning disable CA1416
            switch (EnvironmentEx.OS)
            {
                case OSPlatformID.Win:
                    return new UtilZ.Tool.WindowsAdapter.WindowsAdapter();
                case OSPlatformID.Linux_Unix:
                    return new UtilZ.Tool.LinuxAdapter.LinuxAdapter();
                default:
                case OSPlatformID.MacOS:
                    throw new NotSupportedException();
            }
#pragma warning restore CA1416

            //string osAdapterDirName;
            //switch (EnvironmentEx.OS)
            //{
            //    case OSPlatformID.Win:
            //        osAdapterDirName = "Windows";
            //        break;
            //    case OSPlatformID.Linux_Unix:
            //        osAdapterDirName = "Linux";
            //        break;
            //    default:
            //    case OSPlatformID.MacOS:
            //        throw new NotSupportedException();
            //}

            //string osAdapterDir = PathEx.Combine(PathType.Local, PathEx.GetEntryAssemblyDirectory(), osAdapterDirName);
            //string[] dllFilePathArr = Directory.GetFiles(osAdapterDir, "*Adapter.dll", SearchOption.AllDirectories);
            //string assemblyName;
            //Assembly assembly;
            //Type iosAdapterType = typeof(IOSAdapter);

            //foreach (var dllFilePath in dllFilePathArr)
            //{
            //    try
            //    {
            //        assemblyName = UtilZ.Dotnet.Ex.Base.AssemblyEx.GetAssemblyName(dllFilePath);
            //        if (string.IsNullOrWhiteSpace(assemblyName))
            //        {
            //            continue;
            //        }

            //        if (!assemblyDic.TryGetValue(assemblyName, out assembly))
            //        {
            //            assembly = Assembly.LoadFrom(dllFilePath);
            //            if (assembly == null)
            //            {
            //                continue;
            //            }

            //            assemblyDic.Add(assembly.FullName, assembly);
            //        }

            //        Type[] typeArr = assembly.GetExportedTypes();
            //        foreach (var type in typeArr)
            //        {
            //            if (!type.IsClass || type.IsAbstract)
            //            {
            //                continue;
            //            }

            //            if (type.GetInterface(iosAdapterType.FullName) == null)
            //            {
            //                continue;
            //            }

            //            int noParameterConstructorCount = type.GetConstructors().Where(t => { return t.GetParameters().Length == 0; }).Count();
            //            if (noParameterConstructorCount == 0)
            //            {
            //                Loger.Warn($"类型\"{type.FullName}\"没有无参构造函数,忽略");
            //                continue;
            //            }

            //            return ActivatorEx.CreateInstance<IOSAdapter>(type);
            //        }
            //    }
            //    catch (BadImageFormatException)
            //    {
            //        continue;
            //    }
            //    catch (Exception ex)
            //    {
            //        Loger.Error(ex);
            //    }
            //}

            //return null;
        }

        private void LoadBaseLib()
        {
            var pluginRootDir = PathEx.GetEntryAssemblyDirectory();
            string baseLibDir = PathEx.Combine(PathType.Local, pluginRootDir, "BaseLib");
            if (!Directory.Exists(baseLibDir))
            {
                return;
            }

            var assemblyDic = AppDomain.CurrentDomain.GetAssemblies().ToDictionary(t => { return t.FullName; });
            string[] dllFilePathArr = Directory.GetFiles(baseLibDir, "*.dll", SearchOption.AllDirectories);
            string assemblyName;
            Assembly assembly;

            foreach (var dllFilePath in dllFilePathArr)
            {
                try
                {
                    assemblyName = UtilZ.Dotnet.Ex.Base.AssemblyEx.GetAssemblyName(dllFilePath);
                    if (string.IsNullOrWhiteSpace(assemblyName))
                    {
                        continue;
                    }

                    if (assemblyDic.TryGetValue(assemblyName, out assembly))
                    {
                        continue;
                    }

                    assembly = Assembly.LoadFrom(dllFilePath);
                    if (assembly == null)
                    {
                        continue;
                    }

                    assemblyDic.Add(assembly.FullName, assembly);
                }
                catch (Exception ex)
                {
                    Loger.Error(ex);
                }
            }
        }

        public void Start()
        {
            this._httpServer.Start();
            this._protectorAppProcessMonitor.Start();

            foreach (var plugin in this._pluginOrderList)
            {
                try
                {
                    plugin.Start();
                    Loger.Info($"插件{plugin.ID}-{plugin.Name}初启动完成...");
                }
                catch (Exception ex)
                {
                    Loger.Error(ex, $"插件\"{plugin.Name}\"启动异常...");
                }
            }

            Loger.Info($"http服务已启动,监听地址口为\"{NetHelper.GetIPPortString(this._httpServer.ServerConfig.ListenEndPoint)}\"...");
            Loger.Info("Host服务启动完成...");
        }


        public void Test()
        {
            var osAdapter = new UtilZ.Tool.WindowsAdapter.WindowsAdapter();
            List<float> list = new List<float>();
            for(int i=0;i<10;i++)
            {
                list.Add(osAdapter.GetCpuUsageRate());
            }
            
        }



        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            this._protectorAppProcessMonitor.Dispose();

            foreach (var pluginInfo in this._pluginDic.Values)
            {
                pluginInfo.Dispose();
            }

            this._httpServer.Dispose();

            lock (this._tmpWebSocketList)
            {
                foreach (WebSocketClient webSocket in this._tmpWebSocketList)
                {
                    webSocket.MessageOutput -= WebSocket_MessageOutput;
                    webSocket.ConnectionChanged -= WebSocket_ConnectionChanged;
                    webSocket.Dispose();
                }
                this._tmpWebSocketList.Clear();
            }

            foreach (var list in this._pluginWebSocketClientListdic.Values)
            {
                lock (list)
                {
                    foreach (WebSocketClient client in list)
                    {
                        client.MessageOutput -= WebSocket_MessageOutput;
                        client.ConnectionChanged -= WebSocket_ConnectionChanged;
                        client.Dispose();
                    }
                }
            }
        }
    }
}
